package org.epidroid.epidroid.androsoul.engine.communication;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

import org.epidroid.epidroid.androsoul.engine.AndroSoul;
import org.epidroid.epidroid.androsoul.exceptions.AndroSoulException;
import org.epidroid.epidroid.androsoul.exceptions.AndroSoulUnknownHostException;

import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

/**
 * @author conrath handles basic connection with server
 */
public class Connection {

	public static final String SERVER = "ns-server.epitech.net";

	public enum State {
		OFF, LOGOUT, CONNECTED, CAN_AUTH, AUTHENTIFIED
	}

	// Current state
	State _state = State.OFF;

	// socket
	private Socket _sock;
	// buffer in which to send data
	private PrintWriter _nsOut;
	// buffer in which to read data
	private BufferedReader _nsIn;

	// Data, Is it useful to keep it ?
	private int _socketNumber;
	private String _md5;
	private String _hostClient;
	private int _portClient;
	private int _timestampServer;

	/**
	 * private constructor (singleton)
	 * 
	 * @throws AndroSoulException
	 */
	public Connection() {
		initDefaultData();
	}

	public void initDefaultData() {
		_socketNumber = 0;
		_md5 = "";
		_hostClient = "";
		_portClient = 0;
		_timestampServer = 0;
	}

	/**
	 * Close the socket
	 */
	public void finalize() {
		Close();
	}

	/**
	 * Connect the socket to the server
	 * 
	 * @return true if connection succeeds, false otherwise
	 */
	public void connect() throws AndroSoulException {
		try {
			_sock = new Socket(SERVER, 4242);
			_sock.setKeepAlive(true);
			_nsOut = new PrintWriter(_sock.getOutputStream(), true);
			_nsIn = new BufferedReader(new InputStreamReader(
					_sock.getInputStream()));
		} catch (UnknownHostException e) {
			_state = State.OFF;
			throw new AndroSoulUnknownHostException(
					"Cannot connect to the server: unknown host");
		} catch (Exception e) {
			_state = State.OFF;
			throw new AndroSoulException("Cannot connect to the server");
		}
	}

	/**
	 * Start the listener with the given handler
	 * 
	 * @param handler
	 *            Handler to be given to the listener
	 */
	public void listen(Handler handler, QueryQueue queue) {
		Listener listener = new Listener(handler, _sock, _nsIn, _nsOut, queue);
		listener.start();
	}

	public void initData(Bundle b) {
		_socketNumber = b.getInt("socket");
		_md5 = b.getString("md5");
		_hostClient = b.getString("host_client");
		_portClient = b.getInt("port_client");
		_timestampServer = b.getInt("timestamp");

		// / Set the state to connected
		_state = State.CONNECTED;
	}

	/**
	 * Close the output stream and the socket
	 * 
	 * @return return false if an error occurred, false otherwise
	 */
	public boolean Close() {
		try {
			_nsOut.close();
			_sock.close();
		} catch (IOException e) {
			Log.v(AndroSoul.LOGCAT, e.getMessage());
			return false;
		}
		return true;
	}

	/**
	 * Send the given message to the socket
	 * 
	 * @param q
	 *            message to be sent to the server
	 * @throws AndroSoulException
	 */
	public void Query(String q) throws AndroSoulException {
		// Check that the socket is not closed
		if ((_sock == null) || _sock.isOutputShutdown() || !_sock.isConnected()
				|| _sock.isClosed()) {
			// TODO: we should reconnect
			// connect();
			Log.e(AndroSoul.LOGCAT, "Socket error, STOP");
			return;
		}

		_nsOut.println(q);
		Log.v(AndroSoul.LOGCAT, "SENT: " + q);
		if (_nsOut.checkError()) {
			Log.e(AndroSoul.LOGCAT, "Sending error");
		}
	}

	// / Getters
	public int getSocketNumber() {
		return _socketNumber;
	}

	public String getMd5() {
		return _md5;
	}

	public String getHostClient() {
		return _hostClient;
	}

	public int getPortClient() {
		return _portClient;
	}

	public int getTimestampServer() {
		return _timestampServer;
	}

	public State getState() {
		return _state;
	}

	public void setState(State mState) {
		this._state = mState;
	}

}